import { PropType, defineComponent, onMounted, onUnmounted, provide, reactive, watchEffect } from "vue";
import { scrollTop } from "../utils/assist";

import AnchorLink, { AnchorLinkProps } from './AnchorLink';

export interface AnchorProps {
    container?: string | HTMLElement,
    scrollContainer?: string | HTMLElement,
    scrollOffset?: number,
    offsetTop?: number,
    bounds?: number,
    showInk?: boolean,
    mode?: 'hash'|'history'
}

type AnchorStore = {
    inkTop: number,
    inkHeight: number,
    currentId: string,
    currentLink: string,
    animating: boolean,
    links: AnchorLinkProps[],
    upperFirstTitle?: boolean
}

const Anchor = defineComponent({
    name: 'Anchor',
    props: {
        container: {type: [String, Object] as PropType<AnchorProps['container']>, default: undefined},
        scrollContainer: {type: [String, Object] as PropType<AnchorProps['scrollContainer']>, default: undefined},
        scrollOffset: {type: Number as PropType<AnchorProps['scrollOffset']>, default: undefined},
        offsetTop: {type: Number as PropType<AnchorProps['offsetTop']>, default: undefined},
        bounds: {type: Number as PropType<AnchorProps['bounds']>, default: undefined},
        showInk: {type: Boolean as PropType<AnchorProps['showInk']>, default: undefined},
        mode: {type: String as PropType<AnchorProps['mode']>, default: undefined}
    },
    setup (props: AnchorProps, {slots}) {
        let scrollContainer: any = null;
        let scrollElement: any = null;
        let wrapperTop = 0;
        const bounds: number = props.bounds || 5;
        let titlesOffsetArr: any = [];
        const mode = props.mode ?? 'hash';
        const showInk = props.showInk ?? false;

        const store = reactive({
            inkTop: 0,
            inkHeight: 0,
            currentId: '',
            currentLink: '',
            animating: false,
            links: [],
            upperFirstTitle: true
        } as AnchorStore);

        const gotoAnchor = (href: any, e: any) => {
            e.stopPropagation && e.stopPropagation();
            e.preventDefault && e.preventDefault();

            store.currentLink = href;
            store.currentId = href.replace('#', '');
            handleScrollTo();
            if (mode === 'hash') {
                window.location.hash = href;
            } else {
                const path = window.location.href;
                const search = path.includes('?') ? path.split('?')[1] : '';
                const index = location.hash.indexOf('?');
                const hash = index > -1 ? location.hash.substring(0, index) : location.hash;
                const params = new URLSearchParams(search);
                params.set('_to', href);
                window.history.replaceState({}, '', `${location.pathname}${hash}?${params.toString()}`);
            }
        };

        const handleScrollTo = () => {
            const anchor = document.getElementById(store.currentId);
            const currentLinkElementA: any = document.querySelector(`a[data-href="${store.currentLink}"]`);
            let offset = props.scrollOffset || 0;
            if (currentLinkElementA) {
                offset = parseFloat(currentLinkElementA.getAttribute('data-scroll-offset'));
            }
            if (!anchor) return;
            const offsetTop = anchor.offsetTop - wrapperTop - offset;

            store.animating = true;
            scrollTop(scrollContainer, scrollElement.scrollTop, offsetTop, 600, () => {
                store.animating = false;
            });
        };

        onMounted(() => {
            init();
            const links = store.links.map(item => {
                return item.href;
            });
            const idArr = links.map(link => {
                return link.split('#')[1];
            });
            idArr.forEach((id, index) => {
                const titleEle = document.getElementById(id);

                if (titleEle) {
                    const offset = titleEle.offsetTop - scrollElement.offsetTop;
                    if (titlesOffsetArr[index] && titlesOffsetArr[index].offset !== offset) {
                        titlesOffsetArr[index].offset = offset;
                    }
                }
            });
        });

        const removeListener = () => {
            scrollContainer && scrollContainer.removeEventListener('scroll', handleScroll);
            window.removeEventListener('hashchange', handleHashChange);
        };

        const getContainer = () => {
            scrollContainer = props.container ? (typeof props.container === 'string' ? document.querySelector(props.container) : props.container) : window;
            scrollElement = props.container ? scrollContainer : (document.documentElement || document.body);
        };

        const containerIsWindow = () => scrollContainer === window;

        const init = () => {
            handleHashChange();
            removeListener();
            getContainer();
            wrapperTop = containerIsWindow() ? 0 : scrollElement.offsetTop;
            handleScrollTo();
            scrollContainer.addEventListener('scroll', handleScroll);
            window.addEventListener('hashchange', handleHashChange);
        };

        const handleHashChange = () => {
            let sharpLinkMatch: any;
            if (mode === 'hash') {
                const url = window.location.href;
                sharpLinkMatch = /#([^#]+)$/.exec(url);
            } else {
                const path = window.location.href;
                const search = path.includes('?') ? path.split('?')[1] : '';
                const params = new URLSearchParams(search);
                const has = params.has('_to');
                if (has) {
                    if (params.get('_to')) {
                        sharpLinkMatch = [];
                        sharpLinkMatch[0] = params.get('_to');
                        sharpLinkMatch[1] = params.get('_to')?.replace('#', '');
                    }
                }
            }
            if (!sharpLinkMatch) {
                setTimeout(() => {
                    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                    getCurrentScrollAtTitleId(scrollTop);
                }, 10);
                return;
            };

            store.currentLink = sharpLinkMatch[0];
            store.currentId = sharpLinkMatch[1];
        };

        const handleScroll = (e: any) => {
            // this.upperFirstTitle = !!this.titlesOffsetArr[0] && e.target.scrollTop < this.titlesOffsetArr[0].offset;
            if (store.animating) return;
            // this.updateTitleOffset();
            const scrollTop = (document.documentElement.scrollTop || document.body.scrollTop || e.target.scrollTop);
            getCurrentScrollAtTitleId(scrollTop);
        };

        const getCurrentScrollAtTitleId = (scrollTop: number) => {
            let i = -1;
            const len = titlesOffsetArr.length;
            let titleItem = {
                link: '#',
                offset: 0
            };

            scrollTop += bounds;
            while (++i < len) {
                const currentEle = titlesOffsetArr[i];
                const nextEle = titlesOffsetArr[i + 1];
                if (scrollTop >= currentEle.offset && scrollTop < ((nextEle && nextEle.offset) || Infinity)) {
                    titleItem = titlesOffsetArr[i];
                    break;
                }
            }
            store.currentLink = titleItem.link;
        };

        const addLink = (link) => {
            store.links.push(link);
        };

        onUnmounted(() => {
            removeListener();
        });

        provide('CMAnchorContext', {
            gotoAnchor,
            addLink,
            scrollOffset: props.scrollOffset
        });

        watchEffect(() => {
            store.currentLink;
            const currentLinkElementA: any = document.querySelector(`a[data-href="${store.currentLink}"]`);

            if (!currentLinkElementA) return;
            const elementATop = currentLinkElementA.offsetTop;
            const elementAHeight = currentLinkElementA.getBoundingClientRect().height;
            const offset = elementAHeight / 4;

            const top = (elementATop < 0 ? (props.offsetTop || 0) : elementATop);
            store.inkTop = top + offset / 2;
            store.inkHeight = elementAHeight * 3 / 4;
        });

        watchEffect(() => {
            const links = store.links.map(item => {
                return item.href;
            });
            const idArr = links.map(link => {
                return link.split('#')[1];
            });

            if (!scrollElement) {
                getContainer();
            }

            const arr: any = [];
            idArr.forEach(id => {
                const titleEle = document.getElementById(id);
                if (titleEle) arr.push({
                    link: `#${id}`,
                    offset: titleEle.offsetTop - scrollElement.offsetTop
                });
            });

            titlesOffsetArr = arr;
        });

        return () => <div class="cm-anchor">
            <div class="cm-anchor-wrapper">
                <div class="cm-anchor-inner">
                    <div class={"cm-anchor-ink " + (showInk ? 'cm-anchor-show' : '')}>
                        <span class="cm-anchor-ink-ball" style={{top: `${store.inkTop}px`, height: `${store.inkHeight}px`}}></span>
                    </div>
                    {
                        slots.default?.()
                    }
                </div>
            </div>
        </div>;
    },
});

Anchor.Link = AnchorLink;
export default Anchor;
